package de.szut.util;

import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * Array that allows several extra functions
 * @author Marc Huisinga
 *
 * @param <T> - Type of the array
 */
public class ExtendedArray<T> implements Collection<T>{

	private Object[] data;
	private Iterator<T> iterator;
	
	
	/**
	 * Gets the value at the specified index
	 * @param index
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public T get(int index) {
		return (T) data[index];
	}
	
	@Override
	public boolean add(T value) {
    	for (int index = 0; index != data.length; index ++) {
    		if (data[index] == null) {
    			data[index] = value;
    			return true;
    		}
    	}
    	return false;
	}

	/**
	 * Sets the value at the specified index
	 * @param value
	 * @param index
	 */
	public void set(T value, int index) {
		data[index] = value;
	}
	
	/**
	 * Gets the first index of the specified value, returns -1 if the value wasn't found.
	 * @param value
	 * @return position
	 */
	public int indexOf(T value) {
		for (int index = 0; index != data.length; index ++) {
			if (data[index].equals(value)) {
				return index;
			}
		}
		return -1;
	}
	
	/**
	 * Gets the last index of the specified value, returns -1 if the value wasn't found.
	 * @param value
	 * @return position
	 */
	public int lastIndexOf(T value) {
		for (int index = data.length; index != 0; index --) {
			if (data[index].equals(value)) {
				return index;
			}
		}
		return -1;
	}
	
	@Override
	public boolean addAll(Collection<? extends T> collection) {
		Object[] collectionArray = collection.toArray();
		boolean added = false;
		int collectionIndex = 0;
		for (int index = 0; index != data.length; index ++) {
			if (data[index] == null) {
				data[index] = collectionArray[collectionIndex];
				added = true;
				collectionIndex ++;
			}
		}
    	
    	return added;
	}
	
	/**
	 * Adds a collection to the array. Each element is added to elements in the array that are null.
	 * Starts adding the collection at the specified startIndex.
	 * @param collection - Collection to add
	 * @param startIndex - Index where to start adding (skips everything to this position)
	 * @return array was changed as result to this operation
	 */
	public boolean addAll(Collection<? extends T> collection, int startIndex) {
		Object[] collectionArray = collection.toArray();
		boolean added = false;
		int collectionIndex = 0;
		for (int index = startIndex; index != data.length; index ++) {
			if (data[index] == null) {
				data[index] = collectionArray[collectionIndex];
				added = true;
				collectionIndex ++;
			}
		}
    	
    	return added;
	}

	@Override
	public void clear() {
		for (int index = 0; index != data.length; index ++) {
			data[index] = null;
		}
	}

	@Override
	public boolean contains(Object obj) {
		for (int index = 0; index != data.length; index ++) {
			if (data[index].equals(obj)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean containsAll(Collection<?> collection) {
		boolean containable[] = new boolean[collection.size()];
		int containableIndex = 0;
		boolean firstFound = false;
		Object[] collectionArray = collection.toArray();
    	for (int index = 0; index != data.length; index ++) {
    		if (data[index] != null && data[index].equals(collectionArray[containableIndex])) {
    			if (!firstFound) {
    				firstFound = true;
    			}
    			containable[containableIndex] = true;
    			containableIndex ++;
    			if (containable[collection.size()-1] == true) {
    				return true;
    			}
    		} else if (firstFound) {
				containableIndex = 0;
    			for (int resetIndex = 0; resetIndex != containable.length; resetIndex ++) {
    				containable[resetIndex] = false;
    			}
    			firstFound = false;
    		}
    	}
    	return false;
	}

	@Override
	public boolean isEmpty() {
		for (int index = 0; index != data.length; index ++) {
			if (data[index] != null) {
				return false;
			}
		}
		return true;
	}

	@Override
	public Iterator<T> iterator() {
		return iterator;
	}

	@Override
	public boolean remove(Object obj) {
		for (int index = 0; index != data.length; index ++) {
			if (data[index].equals(obj)) {
				data[index] = null;
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Removes an element at the specified index
	 * @param index
	 */
	public void remove(int index) {
		data[index] = null;
	}
	
	/**
	 * Removes all elements within the specified range
	 * @param min bottom border of the range (inclusive)
	 * @param max top border of the range (exclusive)
	 */
	public void removeRange(int min, int max) {
		for (int index = min; index != max; index ++) {
			data[index] = null;
		}
	}

	@Override
	public boolean removeAll(Collection<?> collection) {
		Object[] collectionArray = collection.toArray();
		boolean removed = false;
		for (int dataIndex = 0; dataIndex != data.length; dataIndex ++) {
			for (Object colObj : collectionArray) {
				if (colObj.equals(data[dataIndex])) {
					data[dataIndex] = null;
					removed = true;
				}
			}

		}
    	
    	return removed;
	}
	
	/**
	 * Removes all content within the array that is contained in the collection
	 * @param collection collection to remove
	 * @param startIndex index where to start at (everything until the startIndex is skipped)
	 * @return array changed as result to this operation
	 */
	public boolean removeAll(Collection<?> collection, int startIndex) {
		Object[] collectionArray = collection.toArray();
		boolean removed = false;
		for (int dataIndex = startIndex; dataIndex != data.length; dataIndex ++) {
			for (Object colObj : collectionArray) {
				if (colObj.equals(data[dataIndex])) {
					data[dataIndex] = null;
					removed = true;
				}
			}

		}
    	
    	return removed;
	}

	@Override
	public boolean retainAll(Collection<?> collection) {
		Object[] collectionArray = collection.toArray();
		boolean removed = false;
		boolean equals;
		for (int dataIndex = 0; dataIndex != data.length; dataIndex ++) {
			equals = false;
			for (int colIndex = 0; colIndex != collectionArray.length && !equals; colIndex ++) {
				if (collectionArray[colIndex].equals(data[dataIndex])) {
					equals = true;
				}
			}
			if (!equals) {
				data[dataIndex] = null;
				removed = true;
			}
		}
    	
    	return removed;
	}
	
	/**
	 * Only keeps the elements in the array that are specified in the collection
	 * @param collection
	 * @param startIndex - index where to start checking, keeps all elements until the startIndex
	 * @return array changed as result to this operation
	 */
	public boolean retainAll(Collection<?> collection, int startIndex) {
		Object[] collectionArray = collection.toArray();
		boolean removed = false;
		boolean equals;
		for (int dataIndex = startIndex; dataIndex != data.length; dataIndex ++) {
			equals = false;
			for (int colIndex = 0; colIndex != collectionArray.length && !equals; colIndex ++) {
				if (collectionArray[colIndex].equals(data[dataIndex])) {
					equals = true;
				}
			}
			if (!equals) {
				data[dataIndex] = null;
				removed = true;
			}
		}
    	
    	return removed;
	}

	@Override
	public int size() {
		return data.length;
	}

	@Override
	public Object[] toArray() {
		return data.clone();
	}

	@SuppressWarnings("unchecked")
	@Override
	public <S> S[] toArray(S[] arg0) {
		return (S[]) data.clone();
	}
	
	public ExtendedArray(int size) {
		data = new Object[size];
		iterator = new ArrayIterator();
	}

	private class ArrayIterator implements Iterator<T> {

		private int position = 0;
		
		@Override
		public boolean hasNext() {
			return position<data.length;
		}

		@SuppressWarnings("unchecked")
		@Override
		public T next() {
			if (position < data.length) {
				Object currentObject = data[position];
				position++;
				return (T) currentObject;
			} else {
			    throw new NoSuchElementException();
			}
		}

		@Override
		public void remove() {
			data[position] = null;
		}
		
	}
}